﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace Wassrile.Dialogs.Pictogram
{
    public partial class PictogramPallet : Control
    {
        public const int pictWidth = 16;
        public const int pictHeight = 16;

        private int _vPos = 0;
        public int VerticalPosition
        {
            get { return _vPos; }
            set
            {
                _vPos = value;
                this.Refresh();
            }
        }

        public int RealHeight
        {
            get
            {
                if (GetWidthCapacity() != prevWidthCapacity)
                    CreateBuffer();
                if (renderingIndex == 0 && buffer != null)
                    return buffer.Height;
                else
                    return 0;
            }
        }

        private string[] _pictograms;
        public string[] Pictograms
        {
            get { return _pictograms; }
            set
            {
                _pictograms = value;
                prevWidthCapacity = 0;
                Refresh();
            }
        }

        public PictogramPallet()
        {
            InitializeComponent();
            SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
            SetStyle(ControlStyles.ResizeRedraw, true);
            SetStyle(ControlStyles.Selectable, true);
            SetStyle(ControlStyles.StandardClick, true);
            SetStyle(ControlStyles.UserPaint, true);
            SetStyle(ControlStyles.AllPaintingInWmPaint, true);
            SetStyle(ControlStyles.Opaque, true);
        }

        public Image buffer = null;

        protected override void OnPaint(PaintEventArgs pe)
        {
            if (Core.PictogramResolver == null || Pictograms == null)
            {
                pe.Graphics.FillRectangle(SystemBrushes.GrayText, new Rectangle(new Point(0,0), this.Size));
                return;
            }
            if (GetWidthCapacity() != prevWidthCapacity)
                CreateBuffer();
            pe.Graphics.FillRectangle(Brushes.White, pe.ClipRectangle);
            if (buffer != null)
            {
                lock (bufferLocker)
                {
                    int drawHeight = buffer.Height < this.Height ? buffer.Height : this.Height;
                    pe.Graphics.DrawImage(buffer,
                        new Rectangle(0, 0, buffer.Width, drawHeight),
                        new Rectangle(0, VerticalPosition, buffer.Width, drawHeight),
                        GraphicsUnit.Pixel);
                }
            }
            if (renderingIndex != 0)
            {
                TextRenderer.DrawText(
                    pe.Graphics,
                    "しばらくお待ちください...",
                    this.Font,
                    new Rectangle(0, 0, this.Width, this.Height),
                    Color.Black,
                    Color.White,
                    TextFormatFlags.EndEllipsis | TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter | TextFormatFlags.SingleLine);
                return;
            }
            using (Brush b = new SolidBrush(Color.FromArgb(100, Color.RoyalBlue)))
            {
                pe.Graphics.FillRectangle(b, overdrawRect);
                pe.Graphics.DrawRectangle(Pens.Blue, overdrawRect);
            }
        }

        public override void Refresh()
        {
            if (this.InvokeRequired)
                this.Invoke(new MethodInvoker(Refresh));
            else
                base.Refresh();
        }

        Rectangle overdrawRect = new Rectangle();

        volatile int renderingIndex = 0;
        volatile int prevWidthCapacity = 0;
        private object bufferLocker = new object();
        private object renderingLocker = new object();
        public int GetWidthCapacity()
        {
            int cap = (int)Math.Floor((double)this.Width / pictWidth);
            if (cap == 0)
                cap = 1;
            return cap;
        }

        public event EventHandler OnBufferUpdated;
        private void CreateBuffer()
        {
            if (Core.PictogramResolver == null || Pictograms == null) return;
            MethodInvoker mi = new MethodInvoker(CreateBufferInvoke);
            mi.BeginInvoke(CreateBufferEnd, mi);
        }

        private void CreateBufferInvoke()
        {
            int curIdx = ++renderingIndex;
            try
            {
                lock (renderingLocker)
                {
                    int widthCapacity = GetWidthCapacity();
                    int height = (int)Math.Ceiling((double)Pictograms.Length / widthCapacity) * pictHeight;
                    int width = widthCapacity * pictWidth;
                    Image ibuffer = new Bitmap(width, height);
                    using (Graphics g = Graphics.FromImage(ibuffer))
                    {
                        g.FillRectangle(Brushes.White, new Rectangle(0, 0, ibuffer.Width, ibuffer.Height));
                        int widthCount = 0;
                        int heightCount = 0;
                        foreach (var code in Pictograms)
                        {
                            if (renderingIndex > curIdx)
                            {
                                //新しいロードが開始されている
                                return;
                            }
                            curIdx = renderingIndex;
                            try
                            {
                                var img = Core.PictogramResolver.GetPictogram(code);
                                g.DrawImage(img, new Point(widthCount * pictWidth, heightCount * pictHeight));
                            }
                            catch { }
                            widthCount++;
                            if (widthCount >= widthCapacity)
                            {
                                heightCount++;
                                widthCount = 0;
                            }
                        }
                    }
                    lock (bufferLocker)
                    {
                        if (buffer != null)
                        {
                            buffer.Dispose();
                            buffer = null;
                        }
                        buffer = ibuffer;
                    }
                    prevWidthCapacity = widthCapacity;
                }
            }
            finally
            {
                renderingIndex--;
            }
        }

        private void CreateBufferEnd(IAsyncResult iar)
        {
            var invoke = iar.AsyncState as MethodInvoker;
            invoke.EndInvoke(iar);
            if (renderingIndex != 0) return;
            this.Invoke(new MethodInvoker(CreateBufferEndInvoke));
        }

        private void CreateBufferEndInvoke()
        {
            this.Refresh();
            if (OnBufferUpdated != null)
                OnBufferUpdated.Invoke(this, new EventArgs());
        }

        protected override void OnMouseMove(MouseEventArgs e)
        {
            if (Pictograms == null) return;
            if ((int)Math.Ceiling((double)e.X / PictogramPallet.pictWidth) > GetWidthCapacity()) return;
            int index = (int)Math.Floor((double)e.X / PictogramPallet.pictWidth);
            int yLoc = (int)Math.Floor((double)(e.Y + VerticalPosition) / PictogramPallet.pictHeight);
            index += GetWidthCapacity() * yLoc;
            if (index >= 0 && index < Pictograms.Length)
            {
                overdrawRect = new Rectangle(
                    e.X - (e.X % PictogramPallet.pictWidth),
                    e.Y + VerticalPosition - ((e.Y + VerticalPosition) % PictogramPallet.pictHeight),
                    PictogramPallet.pictWidth,
                    PictogramPallet.pictHeight);
                overdrawRect.Y -= VerticalPosition;
                if (overdrawRect.Right > this.Width)
                    overdrawRect = new Rectangle();
                Refresh();
            }
        }

        protected override void OnMouseLeave(EventArgs e)
        {
            overdrawRect = new Rectangle();
            Refresh();
        }
    }
}
